package com.gugee.ins.data.monitor.aop.db;

import com.gugee.ins.data.common.constant.EventTypeEnum;
import com.gugee.ins.data.common.msg.EventMsg;
import com.gugee.ins.data.monitor.config.InsMonitorConfig;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.util.StringUtils;

import java.lang.reflect.InvocationTargetException;
import java.text.DateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;

/**
 * mybatis 拦截器
 * @author Huang xin
 */
@Intercepts({@Signature(method = "update", type = Executor.class, args = {MappedStatement.class, Object.class}),
        @Signature(method = "query", type = Executor.class, args = {MappedStatement.class, Object.class,
                RowBounds.class, ResultHandler.class})})
@Slf4j
public class MybatisInterceptor implements Interceptor {

    /**
     * kafka
     */
    @Autowired
    KafkaTemplate<Long, EventMsg> kafkaTemplateMonitor;

    /**
     *
     */
    @Value("${kafka.topics.insMonitor.online}")
    String topic;

    /**
     *
     */
    @Autowired
    InsMonitorConfig insMonitorConfig;

    /**
     *
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 异常信息
        Throwable e = null;
        long start = System.currentTimeMillis();
        try{
            // 继续执行
            return invocation.proceed();
        } catch (Throwable throwable){
            // 获取异常
            e = throwable;
            throw throwable;
        } finally {
            if(e != null){
                badSqlHandler(e,invocation,start);
            }
        }
    }

    /**
     *
     * @param e
     * @param invocation
     * @param startTime
     */
    private void badSqlHandler(Throwable e,Invocation invocation,long startTime){
        try{
            //错误信息
            String errorMsg ;
            Throwable ee;
            String errorClassName;
            if(e.getMessage() == null && e instanceof InvocationTargetException){
                ee = ((InvocationTargetException) e).getTargetException();
            }else {
                ee = e;
            }
            errorClassName = ee.getClass().getName();
            if(!StringUtils.isEmpty(errorClassName)){
                errorClassName = errorClassName.substring(errorClassName.lastIndexOf(".") + 1);
            }
            errorMsg = errorClassName + ":" + ee.getMessage();
            //获取完整sql
            MappedStatement mappedStatement;
            String sqlId;
            String sql;

            mappedStatement = (MappedStatement) invocation.getArgs()[0];
            Object parameter = null;
            if (invocation.getArgs().length > 1) {
                parameter = invocation.getArgs()[1];
            }
            BoundSql boundSql = mappedStatement.getBoundSql(parameter);
            Configuration configuration = mappedStatement.getConfiguration();
            if(errorClassName.contains("BatchExecutorException") && ee.getMessage() != null){
                sqlId = ee.getMessage().substring(0,ee.getMessage().indexOf(" "));
            }else{
                sqlId = mappedStatement.getId();
            }
            sql = showSql(configuration,boundSql);

            // 发送es消息
            sendMsg(errorMsg,errorClassName,sql,sqlId,startTime);
        }catch (Exception ex){
            log.error("mybatis 获取错误sql error:{}",ex.getMessage());
        }
    }

    /**
     *
     * @param errorMsg
     * @param sql
     * @param startTime
     */
    private void sendMsg(String errorMsg,String errorClassName,String sql,String owner,long startTime){
        long nowTime = System.currentTimeMillis();
        EventMsg eventMsg = new EventMsg();
        eventMsg.setId(nowTime);
        eventMsg.setOwner(owner);
        eventMsg.setPosition(sql.length() > insMonitorConfig.getInsMonitorEventSqlLengthMax()
                ? sql.substring(0,insMonitorConfig.getInsMonitorEventSqlLengthMax()) : sql);
        eventMsg.setEventType(EventTypeEnum.SQL_EXCEPTION);
        eventMsg.setEventStatus(errorClassName);

        eventMsg.setContent(errorMsg);
        eventMsg.setSpendMills(nowTime - startTime);

        kafkaTemplateMonitor.send(topic,eventMsg);
    }

    /**
     *
     * @param obj
     * @return
     */
    private String getParameterValue(Object obj) {
        String value;
        if (obj instanceof String) {
            value = "'" + obj + "'";
            value = value.replaceAll("\\\\", "\\\\\\\\");
            value = value.replaceAll("\\$", "\\\\\\$");
            value = value.replaceAll("\\?", "%3F");
        } else if (obj instanceof Date) {
            DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
            value = "'" + formatter.format(obj) + "'";
        } else {
            if (obj != null) {
                value = obj.toString();
            } else {
                value = "";
            }

        }
        return value;
    }

    /**
     *
     * @param configuration
     * @param boundSql
     * @return
     */
    public String showSql(Configuration configuration, BoundSql boundSql) {
        Object parameterObject = boundSql.getParameterObject();
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
        if (!parameterMappings.isEmpty() && parameterObject != null) {
            TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
            if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                sql = sql.replaceFirst("\\?", getParameterValue(parameterObject));

            } else {
                MetaObject metaObject = configuration.newMetaObject(parameterObject);
                for (ParameterMapping parameterMapping : parameterMappings) {
                    String propertyName = parameterMapping.getProperty();
                    if (metaObject.hasGetter(propertyName)) {
                        Object obj = metaObject.getValue(propertyName);
                        sql = sql.replaceFirst("\\?", getParameterValue(obj));
                    } else if (boundSql.hasAdditionalParameter(propertyName)) {
                        Object obj = boundSql.getAdditionalParameter(propertyName);
                        sql = sql.replaceFirst("\\?", getParameterValue(obj));
                    }
                }
            }
        }
        return sql;
    }
}
